2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../jucer_Headers.h"
27 #include "jucer_ColouredElement.h"
28 #include "jucer_GradientPointComponent.h"
29 #include "../../properties/jucer_PositionPropertyBase.h"
30 #include "../../properties/jucer_ColourPropertyComponent.h"
31 #include "jucer_PaintElementUndoableAction.h"
32 #include "jucer_PaintElementPath.h"
33 #include "jucer_ImageResourceProperty.h"
36 //==============================================================================
37 class ElementFillModeProperty
: public ChoicePropertyComponent
,
38 private ChangeListener
41 ElementFillModeProperty (ColouredElement
* const owner_
, const bool isForStroke_
)
42 : ChoicePropertyComponent ("fill mode"),
44 isForStroke (isForStroke_
)
46 choices
.add ("Solid Colour");
47 choices
.add ("Linear Gradient");
48 choices
.add ("Radial Gradient");
49 choices
.add ("Image Brush");
51 owner
->getDocument()->addChangeListener (this);
54 ~ElementFillModeProperty()
56 owner
->getDocument()->removeChangeListener (this);
59 void setIndex (int newIndex
)
61 JucerFillType
fill (isForStroke
? owner
->getStrokeType().fill
62 : owner
->getFillType());
67 fill
.mode
= JucerFillType::solidColour
;
70 fill
.mode
= JucerFillType::linearGradient
;
73 fill
.mode
= JucerFillType::radialGradient
;
76 fill
.mode
= JucerFillType::imageBrush
;
85 owner
->setFillType (fill
, true);
87 owner
->setStrokeFill (fill
, true);
92 switch (isForStroke
? owner
->getStrokeType().fill
.mode
93 : owner
->getFillType().mode
)
95 case JucerFillType::solidColour
:
97 case JucerFillType::linearGradient
:
99 case JucerFillType::radialGradient
:
101 case JucerFillType::imageBrush
:
111 void changeListenerCallback (ChangeBroadcaster
*)
117 ColouredElement
* const owner
;
118 const bool isForStroke
;
121 //==============================================================================
122 class ElementFillColourProperty
: public ColourPropertyComponent
,
123 private ChangeListener
133 ElementFillColourProperty (const String
& name
,
134 ColouredElement
* const owner_
,
135 const ColourType type_
,
136 const bool isForStroke_
)
137 : ColourPropertyComponent (name
, false),
140 isForStroke (isForStroke_
)
142 owner
->getDocument()->addChangeListener (this);
145 ~ElementFillColourProperty()
147 owner
->getDocument()->removeChangeListener (this);
150 void setColour (const Colour
& newColour
)
152 owner
->getDocument()->getUndoManager().undoCurrentTransactionOnly();
154 JucerFillType
fill (isForStroke
? owner
->getStrokeType().fill
155 : owner
->getFillType());
160 fill
.colour
= newColour
;
163 case gradientColour1
:
164 fill
.gradCol1
= newColour
;
167 case gradientColour2
:
168 fill
.gradCol2
= newColour
;
177 owner
->setFillType (fill
, true);
179 owner
->setStrokeFill (fill
, true);
182 const Colour
getColour() const
184 const JucerFillType
fill (isForStroke
? owner
->getStrokeType().fill
185 : owner
->getFillType());
193 case gradientColour1
:
194 return fill
.gradCol1
;
197 case gradientColour2
:
198 return fill
.gradCol2
;
206 return Colours::black
;
209 void resetToDefault()
211 jassertfalse
// option shouldn't be visible
214 void changeListenerCallback (ChangeBroadcaster
*)
220 ColouredElement
* const owner
;
221 const ColourType type
;
222 const bool isForStroke
;
225 //==============================================================================
226 class ElementFillPositionProperty
: public PositionPropertyBase
229 ElementFillPositionProperty (ColouredElement
* const owner_
,
231 ComponentPositionDimension dimension_
,
233 const bool isForStroke_
)
234 : PositionPropertyBase (owner_
, name
, dimension_
, false, false,
235 owner_
->getDocument()->getComponentLayout()),
238 isForStroke (isForStroke_
)
240 owner
->getDocument()->addChangeListener (this);
243 ~ElementFillPositionProperty()
245 owner
->getDocument()->removeChangeListener (this);
248 //==============================================================================
249 void setPosition (const RelativePositionedRectangle
& newPos
)
251 JucerFillType
fill (isForStroke
? owner
->getStrokeType().fill
252 : owner
->getFillType());
255 fill
.gradPos1
= newPos
;
257 fill
.gradPos2
= newPos
;
260 owner
->setFillType (fill
, true);
262 owner
->setStrokeFill (fill
, true);
265 const RelativePositionedRectangle
getPosition() const
267 const JucerFillType
fill (isForStroke
? owner
->getStrokeType().fill
268 : owner
->getFillType());
270 return isStart
? fill
.gradPos1
275 ColouredElement
* const owner
;
276 const bool isStart
, isForStroke
;
279 //==============================================================================
280 class EnableStrokeProperty
: public BooleanPropertyComponent
,
281 public ChangeListener
284 EnableStrokeProperty (ColouredElement
* const owner_
)
285 : BooleanPropertyComponent ("outline", "Outline enabled", "No outline"),
288 owner
->getDocument()->addChangeListener (this);
291 ~EnableStrokeProperty()
293 owner
->getDocument()->removeChangeListener (this);
296 //==============================================================================
297 void setState (bool newState
) { owner
->enableStroke (newState
, true); }
298 bool getState() const { return owner
->isStrokeEnabled(); }
300 void changeListenerCallback (ChangeBroadcaster
*) { refresh(); }
303 ColouredElement
* const owner
;
306 //==============================================================================
307 class StrokeThicknessProperty
: public SliderPropertyComponent
,
308 public ChangeListener
311 StrokeThicknessProperty (ColouredElement
* const owner_
)
312 : SliderPropertyComponent ("outline thickness", 0.1, 200.0, 0.1, 0.3),
315 owner
->getDocument()->addChangeListener (this);
318 ~StrokeThicknessProperty()
320 owner
->getDocument()->removeChangeListener (this);
323 void setValue (double newValue
)
325 owner
->getDocument()->getUndoManager().undoCurrentTransactionOnly();
327 owner
->setStrokeType (PathStrokeType ((float) newValue
,
328 owner
->getStrokeType().stroke
.getJointStyle(),
329 owner
->getStrokeType().stroke
.getEndStyle()),
333 double getValue() const { return owner
->getStrokeType().stroke
.getStrokeThickness(); }
335 void changeListenerCallback (ChangeBroadcaster
*) { refresh(); }
338 ColouredElement
* const owner
;
341 //==============================================================================
342 class StrokeJointProperty
: public ChoicePropertyComponent
,
343 public ChangeListener
346 StrokeJointProperty (ColouredElement
* const owner_
)
347 : ChoicePropertyComponent ("joint style"),
350 choices
.add ("mitered");
351 choices
.add ("curved");
352 choices
.add ("beveled");
354 owner
->getDocument()->addChangeListener (this);
357 ~StrokeJointProperty()
359 owner
->getDocument()->removeChangeListener (this);
362 void setIndex (int newIndex
)
364 const PathStrokeType::JointStyle joints
[] = { PathStrokeType::mitered
,
365 PathStrokeType::curved
,
366 PathStrokeType::beveled
};
368 jassert (newIndex
>= 0 && newIndex
< 3);
370 owner
->setStrokeType (PathStrokeType (owner
->getStrokeType().stroke
.getStrokeThickness(),
372 owner
->getStrokeType().stroke
.getEndStyle()),
378 switch (owner
->getStrokeType().stroke
.getJointStyle())
380 case PathStrokeType::mitered
:
382 case PathStrokeType::curved
:
384 case PathStrokeType::beveled
:
394 void changeListenerCallback (ChangeBroadcaster
*) { refresh(); }
397 ColouredElement
* const owner
;
400 //==============================================================================
401 class StrokeEndCapProperty
: public ChoicePropertyComponent
,
402 public ChangeListener
405 StrokeEndCapProperty (ColouredElement
* const owner_
)
406 : ChoicePropertyComponent ("end-cap style"),
409 choices
.add ("butt");
410 choices
.add ("square");
411 choices
.add ("round");
413 owner
->getDocument()->addChangeListener (this);
416 ~StrokeEndCapProperty()
418 owner
->getDocument()->removeChangeListener (this);
421 void setIndex (int newIndex
)
423 const PathStrokeType::EndCapStyle ends
[] = { PathStrokeType::butt
,
424 PathStrokeType::square
,
425 PathStrokeType::rounded
};
427 jassert (newIndex
>= 0 && newIndex
< 3);
429 owner
->setStrokeType (PathStrokeType (owner
->getStrokeType().stroke
.getStrokeThickness(),
430 owner
->getStrokeType().stroke
.getJointStyle(),
437 switch (owner
->getStrokeType().stroke
.getEndStyle())
439 case PathStrokeType::butt
:
441 case PathStrokeType::square
:
443 case PathStrokeType::rounded
:
453 void changeListenerCallback (ChangeBroadcaster
*) { refresh(); }
456 ColouredElement
* const owner
;
459 //==============================================================================
460 class ImageBrushResourceProperty
: public ImageResourceProperty
<ColouredElement
>
463 ImageBrushResourceProperty (ColouredElement
* const element_
, const bool isForStroke_
)
464 : ImageResourceProperty
<ColouredElement
> (element_
, isForStroke_
? "stroke image"
466 isForStroke (isForStroke_
)
470 //==============================================================================
471 void setResource (const String
& newName
)
475 JucerFillType
type (element
->getStrokeType().fill
);
476 type
.imageResourceName
= newName
;
478 element
->setStrokeFill (type
, true);
482 JucerFillType
type (element
->getFillType());
483 type
.imageResourceName
= newName
;
485 element
->setFillType (type
, true);
489 const String
getResource() const
492 return element
->getStrokeType().fill
.imageResourceName
;
494 return element
->getFillType().imageResourceName
;
501 //==============================================================================
502 class ImageBrushPositionProperty
: public PositionPropertyBase
505 ImageBrushPositionProperty (ColouredElement
* const owner_
,
507 ComponentPositionDimension dimension_
,
508 const bool isForStroke_
)
509 : PositionPropertyBase (owner_
, name
, dimension_
, false, false,
510 owner_
->getDocument()->getComponentLayout()),
512 isForStroke (isForStroke_
)
514 owner
->getDocument()->addChangeListener (this);
517 ~ImageBrushPositionProperty()
519 owner
->getDocument()->removeChangeListener (this);
522 void setPosition (const RelativePositionedRectangle
& newPos
)
526 JucerFillType
type (owner
->getStrokeType().fill
);
527 type
.imageAnchor
= newPos
;
528 owner
->setStrokeFill (type
, true);
532 JucerFillType
type (owner
->getFillType());
533 type
.imageAnchor
= newPos
;
534 owner
->setFillType (type
, true);
538 const RelativePositionedRectangle
getPosition() const
541 return owner
->getStrokeType().fill
.imageAnchor
;
543 return owner
->getFillType().imageAnchor
;
547 ColouredElement
* const owner
;
548 const bool isForStroke
;
551 //==============================================================================
552 class ImageBrushOpacityProperty
: public SliderPropertyComponent
,
553 private ChangeListener
556 ImageBrushOpacityProperty (ColouredElement
* const element_
, const bool isForStroke_
)
557 : SliderPropertyComponent ("opacity", 0.0, 1.0, 0.001),
559 isForStroke (isForStroke_
)
561 element
->getDocument()->addChangeListener (this);
564 ~ImageBrushOpacityProperty()
566 element
->getDocument()->removeChangeListener (this);
569 void setValue (double newValue
)
571 element
->getDocument()->getUndoManager().undoCurrentTransactionOnly();
575 JucerFillType
type (element
->getStrokeType().fill
);
576 type
.imageOpacity
= newValue
;
578 element
->setStrokeFill (type
, true);
582 JucerFillType
type (element
->getFillType());
583 type
.imageOpacity
= newValue
;
585 element
->setFillType (type
, true);
589 double getValue() const
592 return element
->getStrokeType().fill
.imageOpacity
;
594 return element
->getFillType().imageOpacity
;
597 void changeListenerCallback (ChangeBroadcaster
*)
603 ColouredElement
* const element
;
608 //==============================================================================
609 ColouredElement::ColouredElement (PaintRoutine
* owner_
,
611 const bool showOutline_
,
612 const bool showJointAndEnd_
)
613 : PaintElement (owner_
, name
),
614 isStrokePresent (false),
615 showOutline (showOutline_
),
616 showJointAndEnd (showJointAndEnd_
)
620 ColouredElement::~ColouredElement()
624 //==============================================================================
625 void ColouredElement::getEditableProperties (Array
<PropertyComponent
*>& properties
)
627 PaintElement::getEditableProperties (properties
);
628 getColourSpecificProperties (properties
);
631 void ColouredElement::getColourSpecificProperties (Array
<PropertyComponent
*>& properties
)
633 properties
.add (new ElementFillModeProperty (this, false));
635 switch (getFillType().mode
)
637 case JucerFillType::solidColour
:
638 properties
.add (new ElementFillColourProperty ("colour", this, ElementFillColourProperty::solidColour
, false));
641 case JucerFillType::linearGradient
:
642 case JucerFillType::radialGradient
:
643 properties
.add (new ElementFillColourProperty ("colour 1", this, ElementFillColourProperty::gradientColour1
, false));
644 properties
.add (new ElementFillPositionProperty (this, "x1", PositionPropertyBase::componentX
, true, false));
645 properties
.add (new ElementFillPositionProperty (this, "y1", PositionPropertyBase::componentY
, true, false));
646 properties
.add (new ElementFillColourProperty ("colour 2", this, ElementFillColourProperty::gradientColour2
, false));
647 properties
.add (new ElementFillPositionProperty (this, "x2", PositionPropertyBase::componentX
, false, false));
648 properties
.add (new ElementFillPositionProperty (this, "y2", PositionPropertyBase::componentY
, false, false));
651 case JucerFillType::imageBrush
:
652 properties
.add (new ImageBrushResourceProperty (this, false));
653 properties
.add (new ImageBrushPositionProperty (this, "anchor x", PositionPropertyBase::componentX
, false));
654 properties
.add (new ImageBrushPositionProperty (this, "anchor y", PositionPropertyBase::componentY
, false));
655 properties
.add (new ImageBrushOpacityProperty (this, false));
665 properties
.add (new EnableStrokeProperty (this));
669 properties
.add (new StrokeThicknessProperty (this));
673 properties
.add (new StrokeJointProperty (this));
674 properties
.add (new StrokeEndCapProperty (this));
677 properties
.add (new ElementFillModeProperty (this, true));
679 switch (getStrokeType().fill
.mode
)
681 case JucerFillType::solidColour
:
682 properties
.add (new ElementFillColourProperty ("colour", this, ElementFillColourProperty::solidColour
, true));
685 case JucerFillType::linearGradient
:
686 case JucerFillType::radialGradient
:
687 properties
.add (new ElementFillColourProperty ("colour 1", this, ElementFillColourProperty::gradientColour1
, true));
688 properties
.add (new ElementFillPositionProperty (this, "x1", PositionPropertyBase::componentX
, true, true));
689 properties
.add (new ElementFillPositionProperty (this, "y1", PositionPropertyBase::componentY
, true, true));
690 properties
.add (new ElementFillColourProperty ("colour 2", this, ElementFillColourProperty::gradientColour2
, true));
691 properties
.add (new ElementFillPositionProperty (this, "x2", PositionPropertyBase::componentX
, false, true));
692 properties
.add (new ElementFillPositionProperty (this, "y2", PositionPropertyBase::componentY
, false, true));
695 case JucerFillType::imageBrush
:
696 properties
.add (new ImageBrushResourceProperty (this, true));
697 properties
.add (new ImageBrushPositionProperty (this, "stroke anchor x", PositionPropertyBase::componentX
, true));
698 properties
.add (new ImageBrushPositionProperty (this, "stroke anchor y", PositionPropertyBase::componentY
, true));
699 properties
.add (new ImageBrushOpacityProperty (this, true));
710 //==============================================================================
711 const JucerFillType
& ColouredElement::getFillType() throw()
716 class FillTypeChangeAction
: public PaintElementUndoableAction
<ColouredElement
>
719 FillTypeChangeAction (ColouredElement
* const element
, const JucerFillType
& newState_
)
720 : PaintElementUndoableAction
<ColouredElement
> (element
),
723 oldState
= element
->getFillType();
729 getElement()->setFillType (newState
, false);
736 getElement()->setFillType (oldState
, false);
741 JucerFillType newState
, oldState
;
744 void ColouredElement::setFillType (const JucerFillType
& newType
, const bool undoable
)
746 if (fillType
!= newType
)
750 perform (new FillTypeChangeAction (this, newType
),
757 if (fillType
.mode
!= newType
.mode
)
759 owner
->getSelectedElements().changed();
760 siblingComponentsChanged();
769 //==============================================================================
770 bool ColouredElement::isStrokeEnabled() const throw()
772 return isStrokePresent
&& showOutline
;
775 class StrokeEnableChangeAction
: public PaintElementUndoableAction
<ColouredElement
>
778 StrokeEnableChangeAction (ColouredElement
* const element
, const bool newState_
)
779 : PaintElementUndoableAction
<ColouredElement
> (element
),
782 oldState
= element
->isStrokeEnabled();
788 getElement()->enableStroke (newState
, false);
795 getElement()->enableStroke (oldState
, false);
800 bool newState
, oldState
;
803 void ColouredElement::enableStroke (bool enable
, const bool undoable
)
805 enable
= enable
&& showOutline
;
807 if (isStrokePresent
!= enable
)
811 perform (new StrokeEnableChangeAction (this, enable
),
812 "Change stroke mode");
817 isStrokePresent
= enable
;
819 siblingComponentsChanged();
821 owner
->getSelectedElements().changed();
826 //==============================================================================
827 const StrokeType
& ColouredElement::getStrokeType() throw()
832 class StrokeTypeChangeAction
: public PaintElementUndoableAction
<ColouredElement
>
835 StrokeTypeChangeAction (ColouredElement
* const element
, const PathStrokeType
& newState_
)
836 : PaintElementUndoableAction
<ColouredElement
> (element
),
837 newState (newState_
),
838 oldState (element
->getStrokeType().stroke
)
845 getElement()->setStrokeType (newState
, false);
852 getElement()->setStrokeType (oldState
, false);
857 PathStrokeType newState
, oldState
;
860 void ColouredElement::setStrokeType (const PathStrokeType
& newType
, const bool undoable
)
862 if (strokeType
.stroke
!= newType
)
866 perform (new StrokeTypeChangeAction (this, newType
),
867 "Change stroke type");
872 strokeType
.stroke
= newType
;
878 class StrokeFillTypeChangeAction
: public PaintElementUndoableAction
<ColouredElement
>
881 StrokeFillTypeChangeAction (ColouredElement
* const element
, const JucerFillType
& newState_
)
882 : PaintElementUndoableAction
<ColouredElement
> (element
),
885 oldState
= element
->getStrokeType().fill
;
891 getElement()->setStrokeFill (newState
, false);
898 getElement()->setStrokeFill (oldState
, false);
903 JucerFillType newState
, oldState
;
906 void ColouredElement::setStrokeFill (const JucerFillType
& newType
, const bool undoable
)
908 if (strokeType
.fill
!= newType
)
912 perform (new StrokeFillTypeChangeAction (this, newType
),
913 "Change stroke fill type");
919 if (strokeType
.fill
.mode
!= newType
.mode
)
921 siblingComponentsChanged();
922 owner
->getSelectedElements().changed();
925 strokeType
.fill
= newType
;
931 //==============================================================================
932 void ColouredElement::createSiblingComponents()
934 GradientPointComponent
* g1
= new GradientPointComponent (this, false, true);
935 siblingComponents
.add (g1
);
937 GradientPointComponent
* g2
= new GradientPointComponent (this, false, false);
938 siblingComponents
.add (g2
);
940 getParentComponent()->addAndMakeVisible (g1
);
941 getParentComponent()->addAndMakeVisible (g2
);
943 g1
->updatePosition();
944 g2
->updatePosition();
946 if (isStrokePresent
&& showOutline
)
948 GradientPointComponent
* g1
= new GradientPointComponent (this, true, true);
949 siblingComponents
.add (g1
);
951 GradientPointComponent
* g2
= new GradientPointComponent (this, true, false);
952 siblingComponents
.add (g2
);
954 getParentComponent()->addAndMakeVisible (g1
);
955 getParentComponent()->addAndMakeVisible (g2
);
957 g1
->updatePosition();
958 g2
->updatePosition();
962 const Rectangle
<int> ColouredElement::getCurrentBounds (const Rectangle
<int>& parentArea
) const
967 border
= (int) strokeType
.stroke
.getStrokeThickness() / 2 + 1;
969 return position
.getRectangle (parentArea
, getDocument()->getComponentLayout())
970 .expanded (border
, border
);
973 void ColouredElement::setCurrentBounds (const Rectangle
<int>& newBounds
,
974 const Rectangle
<int>& parentArea
,
977 Rectangle
<int> r (newBounds
);
981 const int border
= (int) strokeType
.stroke
.getStrokeThickness() / 2 + 1;
982 r
= r
.expanded (-border
, -border
);
984 r
.setSize (jmax (1, r
.getWidth()), jmax (1, r
.getHeight()));
987 RelativePositionedRectangle
pr (position
);
988 pr
.updateFrom (r
.getX() - parentArea
.getX(),
989 r
.getY() - parentArea
.getY(),
990 r
.getWidth(), r
.getHeight(),
991 Rectangle
<int> (0, 0, parentArea
.getWidth(), parentArea
.getHeight()),
992 getDocument()->getComponentLayout());
993 setPosition (pr
, undoable
);
995 updateBounds (parentArea
);
998 //==============================================================================
999 void ColouredElement::addColourAttributes (XmlElement
* const e
) const
1001 e
->setAttribute ("fill", fillType
.toString());
1002 e
->setAttribute ("hasStroke", isStrokePresent
);
1004 if (isStrokePresent
&& showOutline
)
1006 e
->setAttribute ("stroke", strokeType
.toString());
1007 e
->setAttribute ("strokeColour", strokeType
.fill
.toString());
1011 bool ColouredElement::loadColourAttributes (const XmlElement
& xml
)
1013 fillType
.restoreFromString (xml
.getStringAttribute ("fill", String::empty
));
1015 isStrokePresent
= showOutline
&& xml
.getBoolAttribute ("hasStroke", false);
1017 strokeType
.restoreFromString (xml
.getStringAttribute ("stroke", String::empty
));
1018 strokeType
.fill
.restoreFromString (xml
.getStringAttribute ("strokeColour", String::empty
));
1023 //==============================================================================
1024 void ColouredElement::convertToNewPathElement (const Path
& path
)
1026 if (! path
.isEmpty())
1028 PaintElementPath
* newElement
= new PaintElementPath (getOwner());
1029 newElement
->setToPath (path
);
1030 newElement
->setFillType (fillType
, false);
1031 newElement
->enableStroke (isStrokeEnabled(), false);
1032 newElement
->setStrokeType (getStrokeType().stroke
, false);
1033 newElement
->setStrokeFill (getStrokeType().fill
, false);
1035 XmlElement
* xml
= newElement
->createXml();
1038 PaintElement
* e
= getOwner()->addElementFromXml (*xml
, getOwner()->indexOfElement (this), true);
1041 getOwner()->getSelectedElements().selectOnly (e
);
1042 getOwner()->removeElement (this, true);